#pragma once

/// a simple logger for single thread under C++20 at least

#include "os_helper.h"

#include <string>
#include <string_view>
#include <format>
#include <source_location>
#include <iostream>

#define LOG_LOCATION __FILE__ << ":" << __LINE__

template<class... Args>
using FormatString = std::format_string<Args...>;

template<class T, class Char = char>
struct is_convertible_to_basic_format_string
	: std::integral_constant<bool, std::is_convertible<T, std::basic_string_view<Char>>::value>{};

template <class T>
struct is_convertible_to_any_format_string
	: std::integral_constant<bool,
	is_convertible_to_basic_format_string<T, char>::value ||
	is_convertible_to_basic_format_string<T, wchar_t>::value> {};

enum LogLevel : int {
	NONE = 0,
	TRACER,
	DEBUG,
	INFO,
	WARN,
	ERROR,
	FATAL,
	NUM_LOG_LEVELS,
};

#define LOGLEVEL_NAME_NONE  std::string_view("", 0)
#define LOGLEVEL_NAME_TRACE std::string_view("TRACE", 5)
#define LOGLEVEL_NAME_DEBUG std::string_view("DEBUG", 5)
#define LOGLEVEL_NAME_INFO  std::string_view("INFO", 4)
#define LOGLEVEL_NAME_WARNING std::string_view("WARNING", 7)
#define LOGLEVEL_NAME_ERROR std::string_view("ERROR", 5)
#define LOGLEVEL_NAME_FATAL std::string_view("FATAL", 5)

#ifndef LOGLEVEL_NAMES
#define LOGLEVEL_NAMES                                              \
{                                                                   \
	LOGLEVEL_NAME_NONE, LOGLEVEL_NAME_TRACE, LOGLEVEL_NAME_DEBUG,   \
	LOGLEVEL_NAME_INFO, LOGLEVEL_NAME_WARNING, LOGLEVEL_NAME_ERROR, \
	LOGLEVEL_NAME_FATAL                                             \
}
#endif // !LOGLEVEL_NAMES

static std::string_view levelstrlist[] LOGLEVEL_NAMES;

template <typename T, typename... Args>
constexpr std::basic_string_view<T> to_string_view(
	std::basic_format_string<T, Args...> fmt) noexcept {
	return fmt.get();
}

constexpr std::string_view to_string_view(const std::string& buf) noexcept
{
	return buf;
}

constexpr std::string_view to_string_view(std::string_view str) noexcept
{
	return str;
}

std::string_view level_to_str(LogLevel level);

namespace logging {

	// common implementation for series of API log
	template<class ...Args>
	inline void log_(std::source_location loc, LogLevel level, std::string_view fmt, Args &&...args)
	{
		std::string buf;
		// log level
		if (level != NONE) {
			buf.append("[");
			buf.append(level_to_str(level));
			buf.append("] ");
		}

		// formatted message
		std::vformat_to(std::back_inserter(buf), fmt, std::make_format_args(args...));
		buf.append(" ");

		// location
		std::string suffix = os_helper::simple_filename(loc.file_name());
		buf.append(suffix);
		buf.append(":");
		buf.append(std::to_string(loc.line()));
		// end of a line
		buf.append("\n");

		if (level >= ERROR) {
			std::cerr << buf;
		}
		else {
			std::cout << buf;
		}
	}

	template<class... Args>
	inline void log(std::source_location loc, LogLevel level, std::format_string<Args...> fmt, Args &&...args)
	{
		log_(loc, level, to_string_view(fmt), std::forward<Args>(args)...);
	}

	template<class... Args>
	inline void log(LogLevel level, std::format_string<Args...> fmt, Args &&...args)
	{
		log(std::source_location::current(), level, fmt, std::forward<Args>(args)...);
	}
	
	template<class... Args>
	inline void tracer(std::format_string<Args...> fmt, Args &&...args)
	{
		log(LogLevel::TRACER, fmt, std::forward<Args>(args)...);
	}
	template<class... Args>
	inline void debug(std::format_string<Args...> fmt, Args &&...args)
	{
		log(LogLevel::DEBUG, fmt, std::forward<Args>(args)...);
	}
	template<class... Args>
	inline void info(std::format_string<Args...> fmt, Args &&...args)
	{
		std::cout << "info" << std::endl;
		log(LogLevel::INFO, fmt, std::forward<Args>(args)...);
	}
	template<class... Args>
	inline void warn(std::format_string<Args...> fmt, Args &&...args)
	{
		log(LogLevel::WARN, fmt, std::forward<Args>(args)...);
	}
	template<class... Args>
	inline void error(std::format_string<Args...> fmt, Args &&...args)
	{
		log(LogLevel::ERROR, fmt, std::forward<Args>(args)...);
	}
	template<class... Args>
	inline void fatal(std::format_string<Args...> fmt, Args &&...args)
	{
		log(LogLevel::fatal, fmt, std::forward<Args>(args)...);
	}
}
